﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;

namespace Antioch.Base
{
    public class EventBasedInput
    {
        public delegate void ButtonEvent(PlayerIndex index, Buttons button, EventTypes type, TimeSpan duration);
        public delegate void StickEvent(PlayerIndex index, Sticks stick, EventTypes type, Vector2 delta, TimeSpan duration);
        public delegate void TriggerEvent(PlayerIndex index, Triggers trigger, EventTypes type, float delta, TimeSpan duration);


        public enum EventTypes
        {
            Pressed,
            Held,
            Released,
            None,
        }

        public enum Buttons
        {
            A,
            B,
            X,
            Y,
            LeftShoulder,
            RightShoulder,
            LeftStick,
            RightStick,
            Back,
            Start,
            DPadUp,
            DPadRight,
            DPadDown,
            DPadLeft,

            DNU_LEFTSTICK = 14,
            DNU_RIGHTSTICK = 15,
            DNU_LEFTTRIGGER = 16,
            DNU_RIGHTTRIGGER = 17,
            DNU_MOUSE = 18,
        }

        public enum MouseButtons
        {
            Left,
            Right,
        }

        public enum Sticks
        {
            Left,
            Right,
        }

        public enum Triggers
        {
            Left,
            Right,
        }

        private GamePadState[] gamePadStates = new GamePadState[4];

        public struct ButtonInfo
        {
            public TimeSpan Duration;
            public EventTypes LastEvent;
            public Vector2 StickPosition;
        }

        private ButtonInfo[,] ButtonStates = new ButtonInfo[4, 19];

        public ButtonEvent ButtonEvents = null;
        public StickEvent StickEvents = null;
        public TriggerEvent TriggerEvents = null;

        public ButtonInfo QueryButton(Buttons button, int Player)
        {
            return ButtonStates[Player, (int)button];
        }

        private KeyboardState oldKeyBoardState;
        private Dictionary<Keys,Buttons> KeyBoardMapping = new Dictionary<Keys,Buttons>();

        class KeyboardStickBinding
        {
            public Keys Up, Down, Left, Right;
            public Sticks Stick;
            public Vector2 OldState;
        }

        private List<KeyboardStickBinding> KeyboardStickBindings = new List<KeyboardStickBinding>();
        private Dictionary<Keys, Triggers> KeyTriggerMapping = new Dictionary<Keys, Triggers>();

        public void BindKey(Keys key, Buttons button)
        {
            KeyBoardMapping.Add(key, button);
        }

        public void BindKeyStick(Sticks Stick, Keys Up, Keys Down, Keys Left, Keys Right)
        {
            KeyboardStickBindings.Add(new KeyboardStickBinding { 
                Stick = Stick,
                Up = Up, Down = Down, Left = Left, Right = Right,
                OldState = Vector2.Zero});
        }

        public void BindKeyTrigger(Keys key, Triggers trigger)
        {
            KeyTriggerMapping.Add(key, trigger);
        }

        private MouseState oldMouseState;
        private Dictionary<MouseButtons, Buttons> MouseBinding = new Dictionary<MouseButtons,Buttons>();

        public void BindMouseButton(MouseButtons mouseButton, Buttons button)
        {
            MouseBinding.Add(mouseButton, button);
        }

        void ProcessButton(int player, ButtonState newState, ButtonState oldState, Buttons button, GameTime gameTime)
        {
			
            if (newState == ButtonState.Pressed && oldState == ButtonState.Released)
            {
                ButtonStates[player, (int)button].Duration = new TimeSpan();
                ButtonStates[player, (int)button].LastEvent = EventTypes.Pressed;
                if (ButtonEvents != null)
                    ButtonEvents((PlayerIndex)player, button, EventTypes.Pressed, ButtonStates[player, (int)button].Duration);
            }
            else if (newState == ButtonState.Pressed && oldState == ButtonState.Pressed)
            {
                ButtonStates[player, (int)button].Duration += gameTime.ElapsedGameTime;
                ButtonStates[player, (int)button].LastEvent = EventTypes.Held;

                if (ButtonEvents != null)
                    ButtonEvents((PlayerIndex)player, button, EventTypes.Held, ButtonStates[player, (int)button].Duration);
            }
            else if (newState == ButtonState.Released && oldState == ButtonState.Pressed)
            {
                ButtonStates[player, (int)button].Duration += gameTime.ElapsedGameTime;
                ButtonStates[player, (int)button].LastEvent = EventTypes.Released;
                if (ButtonEvents != null)
                    ButtonEvents((PlayerIndex)player, button, EventTypes.Released, ButtonStates[player, (int)button].Duration);
            }
            else
            {
                ButtonStates[player, (int)button].LastEvent = EventTypes.None;
            }
        }

        void ProcessStick(int player, Vector2 newState, Vector2 oldState, Sticks stick, GameTime gameTime)
        {
            bool newPressed = System.Math.Abs(newState.X) > 0 || System.Math.Abs(newState.Y) > 0;
            bool oldPressed = System.Math.Abs(oldState.X) > 0 || System.Math.Abs(oldState.Y) > 0;
            ButtonStates[player, 14 + (int)stick].StickPosition = newState;

            if (newPressed && !oldPressed)
            {
                ButtonStates[player, 14 + (int)stick].Duration = new TimeSpan();
                ButtonStates[player, 14 + (int)stick].LastEvent = EventTypes.Pressed;
                if (StickEvents != null)
                    StickEvents((PlayerIndex)player, stick, EventTypes.Pressed, newState,
                        ButtonStates[player, 14 + (int)stick].Duration);
            }
            else if (newPressed && oldPressed)
            {
                ButtonStates[player, 14 + (int)stick].Duration += gameTime.ElapsedGameTime;
                ButtonStates[player, 14 + (int)stick].LastEvent = EventTypes.Held;

                if (StickEvents != null)
                    StickEvents((PlayerIndex)player, stick, EventTypes.Held, newState, 
                        ButtonStates[player, 14 + (int)stick].Duration);
            }
            else if (!newPressed && oldPressed)
            {
                ButtonStates[player, 14 + (int)stick].Duration += gameTime.ElapsedGameTime;
                ButtonStates[player, 14 + (int)stick].LastEvent = EventTypes.Released;

                if (StickEvents != null)
                    StickEvents((PlayerIndex)player, stick, EventTypes.Released, newState,
                        ButtonStates[player, 14 + (int)stick].Duration);
            }
            else
            {
                ButtonStates[player, 14 + (int)stick].LastEvent = EventTypes.None;
            }
        }

        void ProcessTrigger(int player, float newState, float oldState, Triggers trigger, GameTime gameTime)
        {
            bool newPressed = newState > 0;
            bool oldPressed = oldState > 0;

            if (newPressed && !oldPressed)
            {
                ButtonStates[player, 16 + (int)trigger].Duration = new TimeSpan();
                ButtonStates[player, 16 + (int)trigger].LastEvent = EventTypes.Pressed;
                if (TriggerEvents != null)
                    TriggerEvents((PlayerIndex)player, trigger, EventTypes.Pressed, newState, 
                        ButtonStates[player, 16 + (int)trigger].Duration);
            }
            else if (newPressed && oldPressed)
            {
                ButtonStates[player, 16 + (int)trigger].Duration += gameTime.ElapsedGameTime;
                ButtonStates[player, 16 + (int)trigger].LastEvent = EventTypes.Held;
                if (TriggerEvents != null)
                    TriggerEvents((PlayerIndex)player, trigger, EventTypes.Held, newState, 
                        ButtonStates[player, 16 + (int)trigger].Duration);
            }
            else if (!newPressed && oldPressed)
            {
                ButtonStates[player, 16 + (int)trigger].Duration += gameTime.ElapsedGameTime;
                ButtonStates[player, 16 + (int)trigger].LastEvent = EventTypes.Released;
                if (TriggerEvents != null)
                    TriggerEvents((PlayerIndex)player, trigger, EventTypes.Released, newState,
                        ButtonStates[player, 16 + (int)trigger].Duration);
            }
            else
            {
                ButtonStates[player, 16 + (int)trigger].LastEvent = EventTypes.None;
            }
        }

        public void Update(GameTime gameTime, PlayerIndex player)
        {
            int i = (int)player;

            if (player == PlayerIndex.One)
            {
                KeyboardState keyBoardState = Keyboard.GetState();

                foreach (KeyValuePair<Keys, Buttons> Binding in KeyBoardMapping)
                {
                    ProcessButton(i, keyBoardState.IsKeyDown(Binding.Key) ? ButtonState.Pressed : ButtonState.Released,
                        oldKeyBoardState.IsKeyDown(Binding.Key) ? ButtonState.Pressed : ButtonState.Released,
                        Binding.Value, gameTime);
                }

                for (int b = 0; b < KeyboardStickBindings.Count; ++b)
                {
                    KeyboardStickBinding KeyStickBinding = KeyboardStickBindings[b];
                    Vector2 StickPosition = Vector2.Zero;
                    if (keyBoardState.IsKeyDown(KeyStickBinding.Up)) StickPosition.Y += 1;
                    if (keyBoardState.IsKeyDown(KeyStickBinding.Down)) StickPosition.Y -= 1;
                    if (keyBoardState.IsKeyDown(KeyStickBinding.Left)) StickPosition.X -= 1;
                    if (keyBoardState.IsKeyDown(KeyStickBinding.Right)) StickPosition.X += 1;
                    ProcessStick(0, StickPosition, KeyStickBinding.OldState, KeyStickBinding.Stick, gameTime);
                    KeyStickBinding.OldState = StickPosition;
                }

                foreach (KeyValuePair<Keys, Triggers> Binding in KeyTriggerMapping)
                {
                    ProcessTrigger(i,
                        keyBoardState.IsKeyDown(Binding.Key) ? 1.0f : 0.0f,
                        oldKeyBoardState.IsKeyDown(Binding.Key) ? 1.0f : 0.0f,
                        Binding.Value, gameTime);
                }

                oldKeyBoardState = keyBoardState;

                var mouseState = Mouse.GetState();

                foreach (var Binding in MouseBinding)
                {
                    switch (Binding.Key)
                    {
                        case MouseButtons.Left:
                            ProcessButton(i, mouseState.LeftButton, oldMouseState.LeftButton, Binding.Value, gameTime);
                            break;
                        case MouseButtons.Right:
                            ProcessButton(i, mouseState.RightButton, oldMouseState.RightButton, Binding.Value, gameTime);
                            break;
                    }

                }

                ButtonStates[0,18].StickPosition = new Vector2(mouseState.X, mouseState.Y);

                oldMouseState = mouseState;
            }



            GamePadState state = GamePad.GetState((PlayerIndex)i, GamePadDeadZone.IndependentAxes);
            if (!state.IsConnected) return;

            ProcessButton(i, state.Buttons.A, gamePadStates[i].Buttons.A, Buttons.A, gameTime);
            ProcessButton(i, state.Buttons.B, gamePadStates[i].Buttons.B, Buttons.B, gameTime);
            ProcessButton(i, state.Buttons.X, gamePadStates[i].Buttons.X, Buttons.X, gameTime);
            ProcessButton(i, state.Buttons.Y, gamePadStates[i].Buttons.Y, Buttons.Y, gameTime);
            ProcessButton(i, state.Buttons.LeftShoulder, gamePadStates[i].Buttons.LeftShoulder, Buttons.LeftShoulder, gameTime);
            ProcessButton(i, state.Buttons.RightShoulder, gamePadStates[i].Buttons.RightShoulder, Buttons.RightShoulder, gameTime);
            ProcessButton(i, state.Buttons.LeftStick, gamePadStates[i].Buttons.LeftStick, Buttons.LeftStick, gameTime);
            ProcessButton(i, state.Buttons.RightStick, gamePadStates[i].Buttons.RightStick, Buttons.RightStick, gameTime);
            ProcessButton(i, state.Buttons.Back, gamePadStates[i].Buttons.Back, Buttons.Back, gameTime);
            ProcessButton(i, state.Buttons.Start, gamePadStates[i].Buttons.A, Buttons.Start, gameTime);
            ProcessButton(i, state.DPad.Up, gamePadStates[i].DPad.Up, Buttons.DPadUp, gameTime);
            ProcessButton(i, state.DPad.Right, gamePadStates[i].DPad.Right, Buttons.DPadRight, gameTime);
            ProcessButton(i, state.DPad.Down, gamePadStates[i].DPad.Down, Buttons.DPadDown, gameTime);
            ProcessButton(i, state.DPad.Left, gamePadStates[i].DPad.Left, Buttons.DPadLeft, gameTime);

            ProcessStick(i, state.ThumbSticks.Left, gamePadStates[i].ThumbSticks.Left, Sticks.Left, gameTime);
            ProcessStick(i, state.ThumbSticks.Right, gamePadStates[i].ThumbSticks.Right, Sticks.Right, gameTime);
            ProcessTrigger(i, state.Triggers.Left, gamePadStates[i].Triggers.Left, Triggers.Left, gameTime);
            ProcessTrigger(i, state.Triggers.Right, gamePadStates[i].Triggers.Right, Triggers.Right, gameTime);

            gamePadStates[i] = state;

            
        }

    }
}
